﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Text;
using Tesla.Core;
using Tesla.Math;

namespace Tesla.Graphics {

#pragma warning disable 1591

    public sealed class SpriteBatch {
        private const int MaxBatchSize = 2048;
        private IRenderer _renderer;
        private IndexBuffer _indexBuffer;
        private VertexBuffer _vertexBuffer;
        private int _spriteVbPos;
        private VertexPositionColorTexture[] _vertices;

        private Sprite[] _spriteQueue;
        private int[] _sortedSpriteIndices;
        private Sprite[] _sortedSprites;
        private int _spriteQueueCount;

        private bool _inBeginEnd;
        private SpriteSortMode _sortMode;
        private Matrix _worldMatrix;
        private Effect _customEffect;
        private Effect _spriteEffect;
        private IEffectParameter _matrixParam;

        //RenderStates
        private BlendState _bs;
        private RasterizerState _rs;
        private SamplerState _ss;
        private DepthStencilState _dss;

        //Comparers
        private TextureComparer _textureComparer;
        private OrthoComparer _frontToBackComparer;
        private OrthoComparer _backToFrontComparer;

        public SpriteBatch() {
            IRenderSystemProvider renderSystem = Engine.Services.GetService<IRenderSystemProvider>();
            _renderer = renderSystem.Renderer;

            _indexBuffer = new IndexBuffer(IndexFormat.SixteenBits, MaxBatchSize * 6, ResourceUsage.Static);

            _vertices = new VertexPositionColorTexture[MaxBatchSize * 4];
            _vertexBuffer = new VertexBuffer(VertexPositionColorTexture.VertexDeclaration, MaxBatchSize * 4, ResourceUsage.Dynamic);
            _spriteVbPos = 0;

            _spriteEffect = renderSystem.DefaultContent.Load<Effect>("SpriteEffect.tebo");
            _matrixParam = _spriteEffect.Parameters["SpriteTransform"];

            _textureComparer = new TextureComparer(this);
            _frontToBackComparer = new OrthoComparer(this, true);
            _backToFrontComparer = new OrthoComparer(this, false);

            _spriteQueue = new Sprite[MaxBatchSize];
            _spriteQueueCount = 0;
            _inBeginEnd = false;
            _sortMode = SpriteSortMode.Deferred;

            CreateIndexData();
        }

        public SpriteBatch(IRenderSystemProvider renderSystem) {
            _renderer = renderSystem.Renderer;

            _indexBuffer = new IndexBuffer(IndexFormat.SixteenBits, MaxBatchSize * 6, ResourceUsage.Static);

            _vertices = new VertexPositionColorTexture[MaxBatchSize * 4];
            _vertexBuffer = new VertexBuffer(VertexPositionColorTexture.VertexDeclaration, MaxBatchSize * 4, ResourceUsage.Dynamic);
            _spriteVbPos = 0;

            _spriteEffect = renderSystem.DefaultContent.Load<Effect>("SpriteEffect.tebo");
            _matrixParam = _spriteEffect.Parameters["SpriteTransform"];

            _textureComparer = new TextureComparer(this);
            _frontToBackComparer = new OrthoComparer(this, true);
            _backToFrontComparer = new OrthoComparer(this, false);

            _spriteQueue = new Sprite[MaxBatchSize];
            _spriteQueueCount = 0;
            _inBeginEnd = false;
            _sortMode = SpriteSortMode.Deferred;

            CreateIndexData();
        }

        private void CreateIndexData() {
            short[] indArray = new short[12288];
            for(short i = 0; i < 2048; i++) {
                short startIndex = (short) (i * 6);
                short index = (short) (i * 4);
                indArray[startIndex] = index;
                indArray[startIndex + 1] = (short) (index + 2);
                indArray[startIndex + 2] = (short) (index + 1);
                indArray[startIndex + 3] = index;
                indArray[startIndex + 4] = (short) (index + 3);
                indArray[startIndex + 5] = (short) (index + 2);
            }
            _indexBuffer.SetData<short>(indArray);
        }

        public void Begin() {
            Begin(SpriteSortMode.Deferred, null, null, null, null,null, Matrix.Identity);
        }

        public void Begin(SpriteSortMode sortMode) {
            Begin(sortMode, null, null, null, null,null, Matrix.Identity);
        }

        public void Begin(SpriteSortMode sortMode, BlendState blendState) {
            Begin(sortMode, blendState, null, null, null, null, Matrix.Identity);
        }

        public void Begin(SpriteSortMode sortMode, BlendState blendState, RasterizerState rasterizerState, SamplerState samplerState, DepthStencilState depthStencilState) {
            Begin(sortMode, blendState, rasterizerState, samplerState, depthStencilState, null, Matrix.Identity);
        }

        public void Begin(SpriteSortMode sortMode, BlendState blendState, RasterizerState rasterizerState, SamplerState samplerState, DepthStencilState depthStencilState, Effect customEffect) {
            Begin(sortMode, blendState, rasterizerState, samplerState, depthStencilState, customEffect, Matrix.Identity);
        }

        public void Begin(SpriteSortMode sortMode, BlendState blendState, RasterizerState rasterizerState, SamplerState samplerState, DepthStencilState depthStencilState, Effect customEffect, Matrix worldMatrix) {
            if(_inBeginEnd) {
                throw new InvalidOperationException("SpriteBatch.End() must be called before another call to Begin() is initiated.");
            }
            _sortMode = sortMode;
            if(blendState == null) {
                _bs = BlendState.AlphaBlendPremultiplied;
            } else {
                _bs = blendState;
            }
            if(rasterizerState == null) {
                _rs = RasterizerState.CullBackClockwiseFront;
            } else {
                _rs = rasterizerState;
            }
            if(samplerState == null) {
                _ss = SamplerState.LinearClamp;
            } else {
                _ss = samplerState;
            }
            if(depthStencilState == null) {
                _dss = DepthStencilState.None;
            } else {
                _dss = depthStencilState;
            }
            _customEffect = customEffect;
            _worldMatrix = worldMatrix;
            _inBeginEnd = true;

            SetRenderStates();
            SetBuffers();
            if(customEffect == null) {
                ApplyEffect();
            }
        }

        public void Draw(Texture2D texture, Vector2 position, Color tintColor) {
            Vector4 dstRect = new Vector4(position.X, position.Y, 1, 1);
            Rectangle srcRect = new Rectangle(0, 0, texture.Width, texture.Height);
            Draw(texture, dstRect, srcRect, Vector2.Zero, true, tintColor, SpriteFlipEffect.None, 0, 0);
        }

        public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color tintColor) {
            Vector4 dstRect = new Vector4(destinationRectangle.X, destinationRectangle.Y, destinationRectangle.Width, destinationRectangle.Height);
            Draw(texture, dstRect, sourceRectangle, Vector2.Zero, false, tintColor, SpriteFlipEffect.None, 0, 0);
        }

        public void Draw(Texture2D texture, Rectangle destinationRectangle, Color tintColor) {
            Vector4 dstRect = new Vector4(destinationRectangle.X, destinationRectangle.Y, destinationRectangle.Width, destinationRectangle.Height);
            Draw(texture, dstRect, null, Vector2.Zero, false, tintColor, SpriteFlipEffect.None, 0, 0);
        }

        public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color tintColor) {
            Vector4 dstRect = new Vector4(position.X, position.Y, 1, 1);
            Draw(texture, dstRect, sourceRectangle, Vector2.Zero, true, tintColor, SpriteFlipEffect.None, 0, 0);
        }

        public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Vector2 origin, Color tintColor, SpriteFlipEffect flipEffect, float angle, float depth) {
            Vector4 dstRect = new Vector4(destinationRectangle.X, destinationRectangle.Y, destinationRectangle.Width, destinationRectangle.Height);
            Draw(texture, dstRect, sourceRectangle, origin, false, tintColor, flipEffect, angle, depth);
        }

        public void Draw(Texture2D texture, Vector2 position, Vector2 scale, Rectangle? sourceRectangle, Vector2 origin, Color tintColor, SpriteFlipEffect flipEffect, float angle, float depth) {
            Vector4 dstRect = new Vector4(position.X, position.Y, scale.X, scale.Y);
            Draw(texture, dstRect, sourceRectangle, origin, true, tintColor, flipEffect, angle, depth);
        }

        public void Draw(Texture2D texture, Vector2 position, float scale, Rectangle? sourceRectangle, Vector2 origin, Color tintColor, SpriteFlipEffect flipEffect, float angle, float depth) {
            Vector4 dstRect = new Vector4(position.X, position.Y, scale, scale);
            Draw(texture, dstRect, sourceRectangle, origin, true, tintColor, flipEffect, angle, depth);
        }

        public void DrawString(SpriteFont font, String text, Vector2 position, Color tintColor) {
            SpriteFont.StringReference str = new SpriteFont.StringReference(text);
            font.DrawString(this, str, position, tintColor, 0, Vector2.Zero, Vector2.One, SpriteFlipEffect.None, 0);
        }

        public void DrawString(SpriteFont font, String text, Vector2 position, Color tintColor, float angle, Vector2 origin, float scale, SpriteFlipEffect flipEffect, float depth) {
            SpriteFont.StringReference str = new SpriteFont.StringReference(text);
            font.DrawString(this, str, position, tintColor, angle, origin, new Vector2(scale, scale), flipEffect, depth);
        }

        public void DrawString(SpriteFont font, String text, Vector2 position, Color tintColor, float angle, Vector2 origin, Vector2 scale, SpriteFlipEffect flipEffect, float depth) {
            SpriteFont.StringReference str = new SpriteFont.StringReference(text);
            font.DrawString(this, str, position, tintColor, angle, origin, scale, flipEffect, depth);
        }

        public void DrawString(SpriteFont font, StringBuilder text, Vector2 position, Color tintColor) {
            SpriteFont.StringReference str = new SpriteFont.StringReference(text);
            font.DrawString(this, str, position, tintColor, 0, Vector2.Zero, Vector2.One, SpriteFlipEffect.None, 0);
        }

        public void DrawString(SpriteFont font, StringBuilder text, Vector2 position, Color tintColor, float angle, Vector2 origin, float scale, SpriteFlipEffect flipEffect, float depth) {
            SpriteFont.StringReference str = new SpriteFont.StringReference(text);
            font.DrawString(this, str, position, tintColor, angle, origin, new Vector2(scale, scale), flipEffect, depth);
        }

        public void DrawString(SpriteFont font, StringBuilder text, Vector2 position, Color tintColor, float angle, Vector2 origin, Vector2 scale, SpriteFlipEffect flipEffect, float depth) {
            SpriteFont.StringReference str = new SpriteFont.StringReference(text);
            font.DrawString(this, str, position, tintColor, angle, origin, scale, flipEffect, depth);
        }

        internal void Draw(Texture2D texture, Vector4 dstRect, Rectangle? srcRect, Vector2 origin, bool scaleDstFromSrc, Color color, SpriteFlipEffect flipEffect, float angle, float depth) {
            if(!_inBeginEnd) {
                throw new InvalidOperationException("SpriteBatch.Begin() must be called before calling Draw()");
            }
            if(texture == null) {
                throw new ArgumentNullException("texture", "Sprite texture cannot be null.");
            }
            if(_spriteQueueCount >= _spriteQueue.Length) {
                Array.Resize<Sprite>(ref _spriteQueue, _spriteQueue.Length * 2);
            }
     
            Sprite sprite;
            sprite.Texture = texture;
            sprite.Color = color;
            sprite.Angle = angle;
            sprite.Origin = origin;
            sprite.OrthoOrder = depth;
            sprite.FlipEffect = flipEffect;
            //Compute our cropping and screen offsets. Scale the screen offsets if user hasn't supplied us with their own dest rectangle
            float dWidth = dstRect.Z;
            float dHeight = dstRect.W;
            //Using only a subset of the texture
            if(srcRect.HasValue) {
                Rectangle rect = srcRect.Value;
                if(rect.Height == 0 || rect.Width == 0) {
                    throw new ArgumentOutOfRangeException("Source rectangle cannot have zero width or height.");
                }
                Vector4 uvOffsets;
                uvOffsets.X = rect.X;
                uvOffsets.Y = rect.Y;
                uvOffsets.Z = rect.Width;
                uvOffsets.W = rect.Height;
                sprite.TextureOffsets = uvOffsets;
                //If scaling dest rect based on src rect width/height
                Vector4 scOffsets;
                scOffsets.X = dstRect.X;
                scOffsets.Y = dstRect.Y;
                if(scaleDstFromSrc) {
                    scOffsets.Z = dstRect.Z * rect.Width;
                    scOffsets.W = dstRect.W * rect.Height;
                } else {
                    scOffsets.Z = dstRect.Z;
                    scOffsets.W = dstRect.W;
                }
                sprite.ScreenOffsets = scOffsets;
            //Otherwise using the whole texture
            } else {
                Vector4 uvOffsets;
                uvOffsets.X = 0f;
                uvOffsets.Y = 0f;
                uvOffsets.Z = texture.Width;
                uvOffsets.W = texture.Height;
                sprite.TextureOffsets = uvOffsets;
                //If scaling dest rect based on texture width/height
                Vector4 scOffsets;
                scOffsets.X = dstRect.X;
                scOffsets.Y = dstRect.Y;
                if(scaleDstFromSrc) {
                    scOffsets.Z = dstRect.Z * texture.Width;
                    scOffsets.W = dstRect.W * texture.Height;
                } else {
                    scOffsets.Z = dstRect.Z;
                    scOffsets.W = dstRect.W;
                }
                sprite.ScreenOffsets = scOffsets;
            }

            //Add sprite to the queue
            _spriteQueue[_spriteQueueCount] = sprite;
            if(_sortMode == SpriteSortMode.Immediate) {
                RenderBatch(texture, _spriteQueue, 0, 1);
            } else {
                _spriteQueueCount++;
            }
        }

        private void RenderBatch(Texture2D texture, Sprite[] sprites, int index, int numBatches) {
            //Setup pass/texture/state setting
            if(_customEffect != null) {
                int passCount = _customEffect.CurrentTechnique.Passes.Count;
                for(int i = 0; i < passCount; i++) {
                    SetTexture(texture);
                    _customEffect.CurrentTechnique.Passes[i].Apply();
                    OnRenderBatch(texture, sprites, index, numBatches);
                }
            } else {
                SetTexture(texture);
                OnRenderBatch(texture, sprites, index, numBatches);
            }
        }

        private void OnRenderBatch(Texture2D texture, Sprite[] sprites, int index, int numBatches) {
            float scaleWidth = 1f / (float) texture.Width;
            float scaleHeight = 1f / (float) texture.Height;
            //Setup the vertices for all the batches. The vertexbuffer acts like a wrap around queue
            while(numBatches > 0) {
                DataWriteOptions writeOptions = DataWriteOptions.NoOverwrite;
                int actCount = numBatches;
                if(actCount > MaxBatchSize - _spriteVbPos) {
                    //Need to split up, so set actual count to the remaining space
                    actCount = MaxBatchSize - _spriteVbPos;
                    //Need to check if actually we've reached the end of the VB,
                    //if so we need to wrap around and set discard
                    if(_spriteVbPos % MaxBatchSize == 0) {
                        writeOptions = DataWriteOptions.Discard;
                        _spriteVbPos = 0;
                        //Reset actual count to the maximum space available, either
                        //requested batch number, or max batch size - whichever is smallest
                        actCount = System.Math.Min(MaxBatchSize, numBatches);
                    }
                }

                //Loop through sprite array, create the geometry
                for(int i = index, j = 0; i < index + actCount; i++) {
                    Sprite sprite = sprites[i];
                    float cos = 1.0f;
                    float sin = 0.0f;
                    float angle = sprite.Angle;
                    if(angle != 0.0f) {
                        cos = (float) System.Math.Cos(angle);
                        sin = (float) System.Math.Sin(angle);
                    }

                    Vector4 scOffsets = sprite.ScreenOffsets;
                    float sx = scOffsets.X;
                    float sy = scOffsets.Y;
                    float sw = scOffsets.Z;
                    float sh = scOffsets.W;

                    Vector4 texOffsets = sprite.TextureOffsets;
                    float tu = texOffsets.X * scaleWidth;
                    float tv = texOffsets.Y * scaleHeight;
                    float tw = texOffsets.Z * scaleWidth;
                    float th = texOffsets.W * scaleHeight;

                    Vector2 origin = sprite.Origin;
                    float oriX = origin.X / texOffsets.Z;
                    float oriY = origin.Y / texOffsets.W;

                    bool flipH = (sprite.FlipEffect & SpriteFlipEffect.FlipHorizontally) == SpriteFlipEffect.FlipHorizontally;
                    bool flipV = (sprite.FlipEffect & SpriteFlipEffect.FlipVertically) == SpriteFlipEffect.FlipVertically;

                    float z = sprite.OrthoOrder;

                    //Apply change of origin to screen width/height for rotations
                    float sw1 = -oriX * sw;
                    float sw2 = (1 - oriX) * sw;
                    float sh1 = -oriY * sh;
                    float sh2 = (1 - oriY) * sh;

                    //top-left
                    Vector3 v = new Vector3((sx + (sw1 * cos)) - (sh1 * sin), (sy + (sw1 * sin)) + (sh1 * cos), z);
                    Vector2 uv = new Vector2(tu, tv);
                    if(flipH) {
                        uv.X = 1.0f - uv.X;
                    }
                    if(flipV) {
                        uv.Y = 1.0f - uv.Y;
                    }
                    _vertices[j++] = new VertexPositionColorTexture(
                        v,
                        sprite.Color,
                        uv
                        );

                    //lower-left
                    v = new Vector3((sx + (sw1 * cos)) - (sh2 * sin), (sy + (sw1 * sin)) + (sh2 * cos), z);
                    uv = new Vector2(tu, tv + th);
                    if(flipH) {
                        uv.X = 1.0f - uv.X;
                    }
                    if(flipV) {
                        uv.Y = 1.0f - uv.Y;
                    }
                    _vertices[j++] = new VertexPositionColorTexture(
                        v,
                        sprite.Color,
                        uv
                        );

                    //lower-right
                    v = new Vector3((sx + (sw2 * cos)) - (sh2 * sin), (sy + (sw2 * sin)) + (sh2 * cos), z);
                    uv = new Vector2(tu + tw, tv + th);
                    if(flipH) {
                        uv.X = 1.0f - uv.X;
                    }
                    if(flipV) {
                        uv.Y = 1.0f - uv.Y;
                    }
                    _vertices[j++] = new VertexPositionColorTexture(
                        v,
                        sprite.Color,
                        uv
                        );

                    //upper-right
                    v = new Vector3((sx + (sw2 * cos)) - (sh1 * sin), (sy + (sw2 * sin)) + (sh1 * cos), z);
                    uv = new Vector2(tu + tw, tv);
                    if(flipH) {
                        uv.X = 1.0f - uv.X;
                    }
                    if(flipV) {
                        uv.Y = 1.0f - uv.Y;
                    }
                    _vertices[j++] = new VertexPositionColorTexture(
                        v,
                        sprite.Color,
                        uv
                        );
                }

                //Write to VB
                int stride = VertexPositionColorTexture.SizeInBytes;
                int offset = _spriteVbPos * stride * 4;
                _vertexBuffer.SetData<VertexPositionColorTexture>(_vertices, 0, actCount * 4, offset, stride, writeOptions);

                //Render
                _renderer.DrawIndexed(PrimitiveType.TriangleList, actCount * 6, _spriteVbPos * 6, 0);

                index += actCount;
                _spriteVbPos += actCount;
                numBatches -= actCount;
            }
        }

        public void End() {
            if(!_inBeginEnd) {
                throw new InvalidOperationException("SpriteBatch.Begin() must be called before another call to End() is initiated.");
            }
            if(_sortMode != SpriteSortMode.Immediate && _spriteQueueCount > 0) {
                Sprite[] sprites;
                if(_sortMode == SpriteSortMode.Deferred) {
                    sprites = _spriteQueue;
                } else {
                    SortQueue();
                    sprites = _sortedSprites; //Set this after sort, since we may be resizing the sorted array
                }
                RenderQueue(sprites);
            }
            _inBeginEnd = false;
        }

        private void SetRenderStates() {
            _renderer.BlendState = _bs;
            _renderer.DepthStencilState = _dss;
            _renderer.RasterizerState = _rs;
        }

        private void SetTexture(Texture texture) {
            _renderer.Textures[0] = texture;
            _renderer.SamplerStates[0] = _ss;
        }

        private void SetBuffers() {
            _renderer.SetVertexBuffer(_vertexBuffer);
            _renderer.SetIndexBuffer(_indexBuffer);
        }

        private void ApplyEffect() {
            Viewport viewport = _renderer.CurrentCamera.Viewport;
            float invWidth = (viewport.Width > 0) ? (1f / ((float) viewport.Width)) : 0f;
            float invHeight = (viewport.Height > 0) ? (-1f / ((float) viewport.Height)) : 0f;
            Matrix matrix = new Matrix();
            matrix.M11 = invWidth * 2f;
            matrix.M22 = invHeight * 2f;
            matrix.M33 = 1f;
            matrix.M44 = 1f;
            matrix.M41 = -1f;
            matrix.M42 = 1f;
            matrix.M41 = matrix.M41 - invWidth;
            matrix.M42 = matrix.M42 - invHeight;

            _spriteEffect.Parameters["SpriteTransform"].SetValue(_worldMatrix * matrix);
            _spriteEffect.CurrentTechnique.Passes[0].Apply();
            _renderer.SamplerStates[0] = _ss;
        }

        private void SortQueue() {
            //Queue indices for sprite sorting, so we don't have to keep copying the Sprite struct
            //all over the place.
            if(_sortedSpriteIndices == null || _sortedSpriteIndices.Length < _spriteQueueCount) {
                _sortedSpriteIndices = new int[_spriteQueueCount];
                _sortedSprites = new Sprite[_spriteQueueCount];
            }
            IComparer<int> comparer = _textureComparer;
            if(_sortMode == SpriteSortMode.BackToFront) {
                comparer = _backToFrontComparer;
            } else if(_sortMode == SpriteSortMode.FrontToBack) {
                comparer = _frontToBackComparer;
            }

            //Set the indices
            for(int i = 0; i < _spriteQueueCount; i++) {
                _sortedSpriteIndices[i] = i;
            }

            //Sort
            Array.Sort<int>(_sortedSpriteIndices, 0, _spriteQueueCount, comparer);
        }

        private void RenderQueue(Sprite[] sprites) {
            int index = 0;
            Texture2D prevTex = null;
            //Loop through the queue and accumulate sprites with the same texture,
            //when we hit a new texture, we render the accumulated sprites.
            for(int i = 0; i < _spriteQueueCount; i++) {
                Texture2D currTex;
                if(_sortMode == SpriteSortMode.Deferred) {
                    currTex = sprites[i].Texture;
                } else {
                    //Using sorted array, grab the sprite from the queue and place it in the sorted array
                    Sprite sprite = _spriteQueue[_sortedSpriteIndices[i]];
                    currTex = sprite.Texture;
                    sprites[i] = sprite;
                }
                //If texture is not the same, we render any accumulated sprites that have
                //not been rendered since they all had the same texture
                if(prevTex == null || prevTex.ResourceID != currTex.ResourceID) {
                    if(i > index) {
                        //Render accumulated sprites with the previous texture
                        RenderBatch(prevTex, sprites, index, i - index);
                    }
                    index = i;
                    prevTex = currTex;
                }
            }
            //Perform final rendering for any sprites not yet rendered
            RenderBatch(prevTex, sprites, index, _spriteQueueCount - index);
            Array.Clear(_spriteQueue, 0, _spriteQueueCount);
            //Remove texture references
            if(_sortMode != SpriteSortMode.Deferred) {
                Array.Clear(_sortedSprites, 0, _spriteQueueCount);
            }
            _spriteQueueCount = 0;
        }

        private struct Sprite {
            public Texture2D Texture;
            public Vector4 TextureOffsets;
            public Vector4 ScreenOffsets;
            public Vector2 Origin;
            public float Angle;
            public float OrthoOrder;
            public SpriteFlipEffect FlipEffect;
            public Color Color;
        }

        private class TextureComparer : IComparer<int> {
            private SpriteBatch _batch;

            public TextureComparer(SpriteBatch batch) {
                _batch = batch;
            }

            public int Compare(int x, int y) {
                Texture2D xTex = _batch._spriteQueue[x].Texture;
                Texture2D yTex = _batch._spriteQueue[y].Texture;

                return (int) (xTex.ResourceID - yTex.ResourceID);
            }
        }

        private class OrthoComparer : IComparer<int> {
            private SpriteBatch _batch;
            private bool _frontToBack;

            public OrthoComparer(SpriteBatch batch, bool frontToback) {
                _batch = batch;
                _frontToBack = frontToback;
            }

            public int Compare(int x, int y) {
                float xDepth = _batch._spriteQueue[x].OrthoOrder;
                float yDepth = _batch._spriteQueue[y].OrthoOrder;
                if(_frontToBack) {
                    if(xDepth > yDepth) {
                        return -1;
                    } else if(xDepth < yDepth) {
                        return 1;
                    }
                } else {
                    if(xDepth < yDepth) {
                        return -1;
                    } else if(xDepth > yDepth) {
                        return 1;
                    }
                }
                return 0;
            }
        }
    }
}
